package com.example.demo.controller;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.example.demo.dto.FooDTO;
import com.example.demo.feign.ApiFeign;
import com.example.demo.feign.OrderService;
import com.example.demo.feign.StorageService;
import com.example.demo.web.AjaxResult;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping(value = "/test")
@Slf4j
public class HomeController implements InitializingBean, DisposableBean, ServletContextAware {

    static {
        log.error("invoke static");
    }

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    private static final String USER_ID = "U100001";

    private static final String COMMODITY_CODE = "C00321";


    private final OrderService orderService;

    private final StorageService storageService;

    private final ApiFeign apiFeign;

    public HomeController(OrderService orderService, StorageService storageService, ApiFeign apiFeign) {
        log.error("invoke constructor {}", this.getClass().getSimpleName());
        this.orderService = orderService;
        this.storageService = storageService;
        this.apiFeign = apiFeign;
    }

    @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx", rollbackFor = Exception.class)
    @GetMapping(value = "/feign", produces = "application/json")
    public AjaxResult feign() {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        log.info(request.getHeader("xxxx"));

        //将给定的请求属性绑定到当前线程。inheritable参数用来设置是否将RequestAttribute公开给子线程继承，即使用InheritableThreadLocal
        //适用场景：多线程下进行feign异步调用
        RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);

        int nextInt = RandomUtil.randomInt(1, 5);
        AjaxResult result = storageService.storage(COMMODITY_CODE, nextInt);

//        throw new RuntimeException();

        if (!result.get(CODE_TAG).equals(HttpStatus.HTTP_OK)) {
            throw new RuntimeException(result.get(MSG_TAG).toString());
        }

        result = orderService.order(USER_ID, COMMODITY_CODE, nextInt);

        if (!result.get(CODE_TAG).equals(HttpStatus.HTTP_OK)) {
            throw new RuntimeException(result.get(MSG_TAG).toString());
        }

        return result;

    }

    @GetMapping("/feign/{ok}")
    public String testFeign(@PathVariable boolean ok) {
        return apiFeign.feign(ok);
    }

    @GetMapping("/feignMethod/{ok}")
    public String testFeignMethod(@PathVariable boolean ok) {
        return apiFeign.feignMethod(ok);
    }

    @PostMapping("/validate")
    public AjaxResult testFeignMethod(@RequestBody @Validated FooDTO fooDTO) {
        return AjaxResult.success(fooDTO);
    }

    /**
     * @PostConstruct对象创建并赋值完成之后调用 在yml中配置熔断规则但不生效，此处使用硬编码编写规则
     */
    @PostConstruct
    public void init() {
        log.error("invoke PostConstruct {}", this.getClass().getSimpleName());
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule feignMethod = new DegradeRule("/feignMethod/{ok}")
                .setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType())
                // Max allowed response time
                .setCount(1)
                // Retry timeout (in second)
                .setTimeWindow(25)
                .setMinRequestAmount(1)
                .setStatIntervalMs(1000);
        DegradeRule feign = new DegradeRule("/feign/{ok}")
                .setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType())
                // Max allowed response time
                .setCount(1)
                // Retry timeout (in second)
                .setTimeWindow(25)
                .setMinRequestAmount(1)
                .setStatIntervalMs(1000);
        rules.add(feignMethod);
        rules.add(feign);

        DegradeRuleManager.loadRules(rules);
        log.error("Degrade rule loaded: {}", rules);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.error("invoke afterPropertiesSet {}", this.getClass().getSimpleName());
    }

    /**
     * 实现 BeanPostProcessor 会导致全局异常失效
     * @throws Exception
     */
//    @Override
//    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//        if (beanName.equals(this.getClass().getSimpleName()))
//            log.error("invoke postProcessBeforeInitialization {}", beanName);
//        return bean;
//    }
//
//    @Override
//    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//        if (beanName.equals(this.getClass().getSimpleName()))
//            log.error("invoke postProcessAfterInitialization {}", beanName);
//        return bean;
//    }

    @Override
    public void destroy() throws Exception {
        log.error("invoke destroy {}", this.getClass().getSimpleName());
    }

    // 容器移除对象之后调用
    @PreDestroy
    public void preDestroy() {
        log.error("invoke preDestroy {}", this.getClass().getSimpleName());
    }


    @Override
    public void setServletContext(ServletContext servletContext) {
        log.error("invoke setServletContext {}", this.getClass().getSimpleName());
    }
}
